package web

import (
	"JK-Junior-Go-Engineer-Camp/webook/internal/domain"
	"JK-Junior-Go-Engineer-Camp/webook/internal/service"
	ijwt "JK-Junior-Go-Engineer-Camp/webook/internal/web/jwt"
	"JK-Junior-Go-Engineer-Camp/webook/pkg/ginx"
	regexp "github.com/dlclark/regexp2"
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"go.uber.org/zap"
	"net/http"
	"time"
)

const (
	emailRegexPattern = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
	// 和 " 比起来，用 ` 看起来就比较清爽
	passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[.$@$!%*#?&])[A-Za-z\d.$@$!%*#?&]{8,72}$`
	// 业务
	bizLogin = "login"
)

type UserHandler struct {
	emailRegexExp    *regexp.Regexp
	passwordRegexExp *regexp.Regexp
	svc              service.UserService
	codeSvc          service.CodeService
	ijwt.Handler
}

// NewUserHandler 这里通过参数传入，保持依赖注入的风格（没太懂依赖注入暂时）
func NewUserHandler(svc service.UserService, codeSvc service.CodeService, hdl ijwt.Handler) *UserHandler {
	return &UserHandler{
		// MustCompile 会变成两个参数，第二个参数大部分时候都用不上，直接None就行
		emailRegexExp:    regexp.MustCompile(emailRegexPattern, regexp.None),
		passwordRegexExp: regexp.MustCompile(passwordRegexPattern, regexp.None),
		svc:              svc,
		codeSvc:          codeSvc,
		Handler:          hdl,
	}
}

// RegisterRoutes 注册路由
func (h *UserHandler) RegisterRoutes(server *gin.Engine) {
	userGroup := server.Group("/users")
	userGroup.POST("/signup", h.SignUp)
	//userGroup.POST("/login", h.Login)
	userGroup.POST("/login", h.LoginJWT)
	userGroup.POST("/logout", h.LogoutJWT)
	userGroup.POST("/edit", h.EditProfile)
	userGroup.GET("/profile", h.Profile)

	// 手机验证码登录相关
	userGroup.POST("/login_sms/code/send", h.SendLoginSMSCode)
	userGroup.POST("/login_sms", h.LoginSMSCode)

	userGroup.GET("/refresh_token", h.RefreshToken)
}

// SignUp 用户注册
func (h *UserHandler) SignUp(ctx *gin.Context) {
	// 放到这里是为了遵循最小化原则，我的结构体，只能自己用
	type SignUpRequest struct {
		Email           string `json:"email"` // tag标签，标识在json格式中字段的key为email
		Password        string `json:"password"`
		ConfirmPassword string `json:"confirmPassword"`
	}
	var req SignUpRequest
	// Bind: 会根据你的Content-Type,来决定怎么样去解析你的请求
	if err := ctx.Bind(&req); err != nil {
		// 而且使用Bind，你这里不用管，如果请求格式不对，会自动帮你返回400
		return
	}
	// 如果正则表达式太复杂超时了，就会返回这个error
	isEmail, err := h.emailRegexExp.MatchString(req.Email)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	if !isEmail {
		ctx.String(http.StatusOK, "邮箱格式错误")
		return
	}
	if req.Password != req.ConfirmPassword {
		ctx.String(http.StatusOK, "两次密码不匹配")
		return
	}
	isPassword, err := h.passwordRegexExp.MatchString(req.Password)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	if !isPassword {
		ctx.String(http.StatusOK, "密码必须包含字母、数字、特殊字符，并且不少于8位，不超过72位")
		return
	}
	err = h.svc.Signup(ctx, domain.User{
		Email:    req.Email,
		Password: req.Password,
	})
	// 判定邮箱冲突
	switch err {
	case nil:
		ctx.String(http.StatusOK, "注册成功")
	case service.ErrDuplicateEmail:
		ctx.String(http.StatusOK, err.Error())
	default:
		ctx.String(http.StatusOK, "系统错误")
	}
}

// Login 登录-通过Session鉴权
func (h *UserHandler) Login(ctx *gin.Context) {
	type Request struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}
	var req Request
	if err := ctx.Bind(&req); err != nil {
		// 如果绑定数据失败，Bind会帮你返回400的
		return
	}
	user, err := h.svc.Login(ctx, req.Email, req.Password)
	switch err {
	case nil:
		session := sessions.Default(ctx)
		session.Set("userId", user.Id)
		session.Options(sessions.Options{
			MaxAge: 900, // 有效期
		})
		err = session.Save()
		if err != nil {
			ctx.String(http.StatusOK, "系统错误")
			return
		}
		ctx.String(http.StatusOK, "登录成功")
	case service.ErrInvalidUserOrPassword:
		ctx.String(http.StatusOK, err.Error())
	default:
		ctx.String(http.StatusOK, "系统错误")
	}
}

// LoginJWT 登录-通过JWT做鉴权
func (h *UserHandler) LoginJWT(ctx *gin.Context) {
	type Request struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}
	var req Request
	if err := ctx.Bind(&req); err != nil {
		// 如果绑定数据失败，Bind会帮你返回400的
		return
	}
	user, err := h.svc.Login(ctx, req.Email, req.Password)
	switch err {
	case nil:
		err = h.SetLoginToken(ctx, user.Id)
		if err != nil {
			ctx.String(http.StatusOK, "系统错误")
			return
		}
		ctx.String(http.StatusOK, "登录成功")
	case service.ErrInvalidUserOrPassword:
		ctx.String(http.StatusOK, err.Error())
	default:
		ctx.String(http.StatusOK, "系统错误")
	}
}

// EditProfile 修改个人信息
// 注意，其它字段，尤其是密码、邮箱和手机，都要通过别的手段修改，不能通过这个接口
// 邮箱和手机都要验证,密码更加不用多说了
func (h *UserHandler) EditProfile(ctx *gin.Context) {
	type Request struct {
		Nickname string `json:"nickname"`
		Birthday string `json:"birthday"`
		AboutMe  string `json:"aboutMe"`
	}
	var req Request
	if err := ctx.Bind(&req); err != nil {
		// 如果绑定数据失败，Bind会帮你返回400的
		return
	}

	if req.Nickname == "" {
		ctx.JSON(http.StatusOK, gin.H{
			"code": 4,
			"msg":  "用户昵称不能为空",
		})
	}
	if len(req.Nickname) > 36 {
		ctx.JSON(http.StatusOK, gin.H{
			"code": 4,
			"msg":  "用户昵称过长",
		})
		return
	}
	if len(req.AboutMe) > 200 {
		ctx.JSON(http.StatusOK, gin.H{
			"code": 4,
			"msg":  "个人简介过长",
		})
		return
	}
	birthday, err := time.Parse(time.DateOnly, req.Birthday)
	if err != nil {
		ctx.JSON(http.StatusOK, gin.H{
			"code": 4,
			"msg":  "生日格式错误",
		})
		return
	}

	session := sessions.Default(ctx)
	userId := session.Get("userId").(int64)
	err = h.svc.UpdateNonSensitiveInfo(ctx, domain.User{
		Id:       userId,
		Nickname: req.Nickname,
		Birthday: birthday,
		AboutMe:  req.AboutMe,
	})
	if err != nil {
		ctx.JSON(http.StatusOK, gin.H{
			"code": 5,
			"msg":  "系统错误",
		})
		return
	}
	ctx.JSON(http.StatusOK, gin.H{"msg": "修改成功"})
}

// Profile 个人信息
func (h *UserHandler) Profile(ctx *gin.Context) {
	userClaims := ctx.MustGet("user").(ijwt.UserClaims)

	user, err := h.svc.Profile(ctx, userClaims.UserId)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	type Response struct {
		Nickname string
		Email    string
		AboutMe  string
		Birthday string
	}
	ctx.JSON(http.StatusOK, Response{
		Nickname: user.Nickname,
		Email:    user.Email,
		AboutMe:  user.AboutMe,
		Birthday: user.Birthday.Format(time.DateOnly),
	})
}

// SendLoginSMSCode 发送短信验证码
func (h *UserHandler) SendLoginSMSCode(ctx *gin.Context) {
	type Req struct {
		Phone string `json:"phone"`
	}
	var req Req
	if err := ctx.Bind(&req); err != nil {
		// 因为如果数据绑定失败，Bind方法会帮我们返回一个400的响应
		return
	}
	if req.Phone == "" {
		ctx.JSON(http.StatusOK, ginx.Result{
			Code: 4, // 代表用户端输入错误
			Msg:  "请输入手机号",
		})
		return
	}
	err := h.codeSvc.Send(ctx, bizLogin, req.Phone)
	switch err {
	case nil:
		ctx.JSON(http.StatusOK, ginx.Result{
			Msg: "发送成功",
		})
	case service.ErrCodeSendTooMany:
		ctx.JSON(http.StatusOK, ginx.Result{
			Code: 4,
			Msg:  "短信发送太频繁，请稍后再试",
		})
		zap.L().Warn("用户发送短信验证码太频繁")
	default:
		ctx.JSON(http.StatusOK, ginx.Result{
			Code: 5, // 系统错误
			Msg:  "系统错误",
		})
		// TODO: 补日志
	}
}

// LoginSMSCode 通过短信验证码登录
func (h *UserHandler) LoginSMSCode(ctx *gin.Context) {
	type Req struct {
		Phone string `json:"phone"`
		Code  string `json:"code"`
	}
	var req Req
	if err := ctx.Bind(&req); err != nil {
		return
	}

	// 不用校验 Phone和Code是否格式正确，如果错误，Redis回来的肯定是失败
	ok, err := h.codeSvc.Verify(ctx, bizLogin, req.Phone, req.Code)
	if err != nil {
		ctx.JSON(http.StatusOK, ginx.Result{
			Code: 5,
			Msg:  "系统异常",
		})
		zap.L().Error("用户手机号登录失败", zap.Error(err))
		return
	}
	if !ok {
		ctx.JSON(http.StatusOK, ginx.Result{
			Code: 4,
			Msg:  "验证码错误，请重新输入",
		})
		return
	}

	// 验证通过：登录或者注册用户
	user, err := h.svc.FindOrCreate(ctx, req.Phone)
	if err != nil {
		ctx.JSON(http.StatusOK, ginx.Result{
			Code: 5,
			Msg:  "系统异常",
		})
		return
	}
	err = h.SetLoginToken(ctx, user.Id)
	if err != nil {
		ctx.String(http.StatusOK, "系统错误")
		return
	}
	ctx.JSON(http.StatusOK, ginx.Result{
		Msg: "登录成功",
	})
}

// RefreshToken 根据长token获取新的短token
func (h *UserHandler) RefreshToken(ctx *gin.Context) {
	refreshTokenStr := h.ExtractToken(ctx)
	var refreshClaims ijwt.RefreshClaims
	// 这个token是jwt包下的结构体
	token, err := jwt.ParseWithClaims(refreshTokenStr, &refreshClaims,
		func(token *jwt.Token) (interface{}, error) {
			return ijwt.RefreshJWTKey, nil
		})
	if err != nil { // token是伪造的
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	if token == nil || !token.Valid { // token可能是非法或者过期了
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	// 检查ssid是否有效
	err = h.CheckSession(ctx, refreshClaims.Ssid)
	if err != nil {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	err = h.SetJWTToken(ctx, refreshClaims.UserId, refreshClaims.Ssid)
	if err != nil {
		ctx.AbortWithStatus(http.StatusUnauthorized)
		return
	}
	ctx.JSON(http.StatusOK, ginx.Result{
		Msg: "OK",
	})
}

// LogoutJWT 退出登录
func (h *UserHandler) LogoutJWT(ctx *gin.Context) {
	err := h.ClearToken(ctx)
	if err != nil {
		ctx.JSON(http.StatusOK, ginx.Result{
			Code: 5,
			Msg:  "系统错误",
		})
		return
	}
	ctx.JSON(http.StatusOK, ginx.Result{Msg: "退出登录成功"})
}
